package com.chenl.interview.controller;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chenl.interview.common.*;
import com.chenl.interview.constant.UserConstant;
import com.chenl.interview.exception.BusinessException;
import com.chenl.interview.manager.CounterManager;
import com.chenl.interview.mapper.QuestionEsDao;
import com.chenl.interview.model.dto.question.QuestionAddRequest;
import com.chenl.interview.model.dto.question.QuestionQueryRequest;
import com.chenl.interview.model.dto.question.QuestionUpdateRequest;
import com.chenl.interview.model.dto.questionBank.QuestionBankQueryRequest;
import com.chenl.interview.model.entity.Question;
import com.chenl.interview.model.entity.QuestionBank;
import com.chenl.interview.model.entity.QuestionBankQuestion;
import com.chenl.interview.model.entity.User;
import com.chenl.interview.model.vo.QuestionVO;
import com.chenl.interview.service.QuestionBankQuestionService;
import com.chenl.interview.service.QuestionService;
import com.chenl.interview.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 面试题
 */
@RestController
@RequestMapping("/question")
@Api
@Slf4j
public class QuestionController {

    @Autowired
    private QuestionService questionService;

    @Autowired
    private QuestionBankQuestionService questionBankQuestionService;

    @Resource
    private QuestionEsDao questionEsDao;

    @Resource
    private CounterManager counterManager;

    @Resource
    private UserService userService;


    @PostMapping("/add")
    @ApiOperation(value = "新增题目")
    @SaCheckRole(UserConstant.ADMIN_ROLE)
    public BaseResponse<Long> addQuestion(@RequestBody QuestionAddRequest questionAddRequest){
        //参数校验
        ThrowUtils.throwIf(questionAddRequest == null,new BusinessException(ErrorCode.PARAMS_ERROR));
        Long questionId = questionService.addQuestion(questionAddRequest);
        return ResultUtils.success(questionId);
    }

    @PostMapping("/delete")
    @ApiOperation(value = "删除题目")
    @SaCheckRole(UserConstant.ADMIN_ROLE)
    @Transactional
    public BaseResponse<Boolean> deleteQuestion(@RequestBody DeleteRequest deleteRequest){
        //参数检验
        ThrowUtils.throwIf(deleteRequest == null,new BusinessException(ErrorCode.PARAMS_ERROR));

        Long id = deleteRequest.getId();
        Question question = questionService.getById(id);
        ThrowUtils.throwIf(question == null,new BusinessException(ErrorCode.NOT_FOUND_ERROR));
        boolean result = questionService.removeById(id);
        ThrowUtils.throwIf(!result,new BusinessException(ErrorCode.OPERATION_ERROR));

        QueryWrapper<QuestionBankQuestion> query = new QueryWrapper<>();
        query.eq("questionId",id);
        boolean isRemove = questionBankQuestionService.remove(query);
        ThrowUtils.throwIf(!isRemove,ErrorCode.OPERATION_ERROR);

        //ES同步删除
        List<Long> ids = Stream.of(id).collect(Collectors.toList());
        questionService.incSyncQuestionToEs(ids);

        return ResultUtils.success(result);
    }

    @PostMapping("/update")
    @SaCheckRole(UserConstant.ADMIN_ROLE)
    @ApiOperation(value = "修改题目")
    public BaseResponse<Boolean> updateQuestion(@RequestBody QuestionUpdateRequest questionUpdateRequest){
        //参数校验
        ThrowUtils.throwIf(questionUpdateRequest == null,new BusinessException(ErrorCode.PARAMS_ERROR));

        Question question = BeanUtil.copyProperties(questionUpdateRequest, Question.class);
        List<String> tags = questionUpdateRequest.getTags();
        if (tags != null){
            String jsonStr = JSONUtil.toJsonStr(tags);
            question.setTags(jsonStr);
        }

        boolean result = questionService.updateQuestion(question);

        return ResultUtils.success(result);
    }

    // TODO: 2025/1/10 分页获取题目封装加入限流熔断，测试成功后更新（编程式定义资源）
    @PostMapping("/list/page/vo")
    @SaCheckLogin
    @ApiOperation(value = "分页获取题目封装")
    @SentinelResource(value = "listQuestionVOByPage",blockHandler = "handleBlockException",fallback = "handleFallback")
    public BaseResponse<Page<QuestionVO>> listQuestionVOByPage(@RequestBody QuestionQueryRequest questionQueryRequest, HttpServletRequest request){
        ThrowUtils.throwIf(questionQueryRequest == null,new BusinessException(ErrorCode.PARAMS_ERROR));
        int current = questionQueryRequest.getCurrent();
        int pageSize = questionQueryRequest.getPageSize();

        Entry entry = null;
        //用户ip
        String remoteAddr = request.getRemoteAddr();
        try {
            entry = SphU.entry("listQuestionVOByPage", EntryType.IN, 1, remoteAddr);
            // 被保护的业务逻辑
            QueryWrapper<Question> queryWrapper = questionService.getQueryWrapper(questionQueryRequest);
            Page<Question> page = questionService.page(new Page<>(current, pageSize), queryWrapper);
            return ResultUtils.success(questionService.getQuestionVOPage(page));
        } catch (Throwable ex) {
            // 业务异常
            if (!BlockException.isBlockException(ex)) {
                Tracer.trace(ex);
                return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");
            }
            // 降级操作（熔断）
            if (ex instanceof DegradeException) {
                return handleFallback(questionQueryRequest, request, ex);
            }
            // 限流操作
            return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "访问过于频繁，请稍后再试");
        } finally {
            if (entry != null) {
                entry.exit(1, remoteAddr);
            }
        }
    }
    /**
     * listQuestionVOByPage 降级操作：直接返回本地数据
     */
    public BaseResponse<Page<QuestionVO>> handleFallback(QuestionQueryRequest questionQueryRequest,
                                                         HttpServletRequest request, Throwable ex) {
        // 可以返回本地数据或空数据
        return ResultUtils.success(null);
    }

    @GetMapping("/get/vo/{id}")
    @ApiOperation(value = "根据id获取题目封装类")
    @SaCheckLogin
    public BaseResponse<QuestionVO> getQuestionVOById(@PathVariable Long id){
        //参数校验
        if (id == null || id < 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        User user = (User) StpUtil.getSession().get(UserConstant.USER_LOGIN_STATE);
        ThrowUtils.throwIf(user == null,new BusinessException(ErrorCode.OPERATION_ERROR,"用户数据不存在"));
        crawlerDetect(user.getId());

        QuestionVO questionVO = questionService.getQuestionVOById(user,id);

        ThrowUtils.throwIf(questionVO == null,new BusinessException(ErrorCode.NOT_FOUND_ERROR));

        return ResultUtils.success(questionVO);
    }

    // TODO: 2025/1/2 检测爬虫
    /**
     * 检测爬虫
     *
     * @param loginUserId
     */
    private void crawlerDetect(long loginUserId) {
        // 调用多少次时告警
        final int WARN_COUNT = 10;
        // 超过多少次封号
        final int BAN_COUNT = 20;
        // 拼接访问 key
        String key = String.format("user:access:%s", loginUserId);
        // 一分钟内访问次数，180 秒过期
        long count = counterManager.incrAndGetCounter(key, 1, TimeUnit.MINUTES, 180);
        // 是否封号
        if (count > BAN_COUNT) {
            // 踢下线
            StpUtil.kickout(loginUserId);
            // 封号
            User updateUser = new User();
            updateUser.setId(loginUserId);
            updateUser.setUserRole("ban");
            userService.updateById(updateUser);
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "访问太频繁，已被封号");
        }
        // 是否告警
        if (count == WARN_COUNT) {
            // 可以改为向管理员发送邮件通知
            throw new BusinessException(110, "警告访问太频繁");
        }
    }

    // TODO: 2024/12/29 分页获取题目列表（仅管理员）

    // TODO: 2025/1/2 es搜索框请求
    @PostMapping("/search/page/vo")
    @ApiOperation(value = "es搜索")
    public BaseResponse<Page<QuestionVO>> searchQuestionVOByPage(@RequestBody QuestionQueryRequest questionQueryRequest) {
        long size = questionQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 200, ErrorCode.PARAMS_ERROR);
        Page<QuestionVO> questionVOPage = questionService.searchFromEs(questionQueryRequest);
        return ResultUtils.success(questionVOPage);
    }

    // TODO: 2025/1/4 批量新增题目（仅管理员）

    // TODO: 2025/1/2 批量删除题目（仅管理员）

    // TODO: 2024/12/31 用户点赞题目

    // TODO: 2024/12/31 用户收藏题目

}
